
//	------------------------------------------------------------------------------
//                                       _                          
//              _ __ ___   ___ _ __ ___ | |__  _ __ __ _ _ __   ___ 
//             | '_ ` _ \ / _ \ '_ ` _ \| '_ \| '__/ _` | '_ \ / _ \
//             | | | | | |  __/ | | | | | |_) | | | (_| | | | |  __/
//             |_| |_| |_|\___|_| |_| |_|_.__/|_|  \__,_|_| |_|\___|
//                                                     
//	------------------------------------------------------------------------------

#region - License -
//
//	------------------------------------------------------------------------------                                                   
//	         Copyright (c) 2010 Peter Vyvey (peter.vyvey@chiliware.be)
//	------------------------------------------------------------------------------
//
//
//	This file is part of CHILIWARE MEMBRANE.
//
//	CHILIWARE MEMBRANE is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Lesser General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	CHILIWARE MEMBRANE is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public License
//	along with CHILIWARE MEMBRANE.  If not, see <http://www.gnu.org/licenses/>.
//
//
#endregion

#region - Changes -
//
//	peter.vyvey@chiliware.be		2010-08-10		Created
//
#endregion

#region - Using -

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

#if (!SILVERLIGHT)

using System.Web;

#else

using System.Windows;

#endif

#endregion

namespace Chiliware.Membrane.Runtime.Reflection
{
	#region - Discovery -

	/// <summary>
	/// Discovers types and attributes on types.
	/// </summary>
	/// <typeparam name="T">The type of the result list items.</typeparam>
	public static class Discovery<T> where T : class
	{
        #region - Private static fields -

		/// <summary>
		/// Lock object for the assembly black list.
		/// </summary>
        private static readonly object LOCK_ASSEMBLY_BLACK_LIST = new object();

		/// <summary>
		/// Lock object for the known types.
		/// </summary>
        private static readonly object LOCK_ASSEMBLY_KNOWN_TYPES = new object();

		/// <summary>
		/// The assembly blacklist including those that caused some exception while scanning the first time.
		/// </summary>
        private static readonly List<string> assembyBlackList = new List<string>();

		/// <summary>
		/// Cache for the known types in assemblies.
		/// </summary>
        private static readonly Dictionary<string, List<Type>> assembyKnownTypes = new Dictionary<string, List<Type>>();

		/// <summary>
		/// Backing store field for the DEFAULTPATH property.
		/// </summary>
		private static string defaultPath = string.Empty;

        #endregion

		#region - Static properties -

		/// <summary>
		/// The default runtime path of the application.
		/// </summary>
		public static string DefaultPath
		{
            get 
			{
				if (string.IsNullOrEmpty(defaultPath))
				{
#if SILVERLIGHT

                    defaultPath = Application.Current.Host.Source.AbsolutePath;
#else
                    // Evaluate if there is need to have an entry in APP.CONFIG to avoid the following TRY/CATCH block
					try
					{
						defaultPath = HttpRuntime.CodegenDir;
					}
					catch
					{
						defaultPath = Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
					}
#endif
                }

				return defaultPath;
			}
			set { defaultPath = value; }

		}

		#endregion

		#region - Public static methods -

		/// <summary>
		/// 
		/// </summary>
		/// <param name="type">The type to inspect.</param>
		/// <param name="attributeType">The attribute type to verify.</param>
		/// <returns>The list of attributes, of the given attribute type, decorating the type.</returns>
		public static List<object> DiscoverAttribute(Type type, Type attributeType)
		{
			List<object> result = new List<object>();

			foreach (Attribute attribute in type.GetCustomAttributes(true))
			{
				if (attribute.GetType() == attributeType)
				{
					result.Add(attribute);
				}
			}

			return result;
		}

		/// <summary>
		/// Discovers the type implementing the given interface type.
		/// </summary>
		/// <param name="type">The interface type.</param>
		/// <returns>A list of runtime type implementing the interface.</returns>
		public static List<T> DiscoverInterface(Type type)
		{
			return DiscoverInterface(type, false, Discovery<T>.DefaultPath);
		}

		/// <summary>
		/// Discovers the type implementing the given interface type.
		/// </summary>
		/// <param name="type">The interface type.</param>
		/// <param name="includeAbstract">TRUE if the abstract types should be included in the resulting list.</param>
		/// <returns>A list of runtime type implementing the interface.</returns>
		public static List<T> DiscoverInterface(Type type, bool includeAbstract)
		{
			return DiscoverInterface(type, includeAbstract, Discovery<T>.DefaultPath);
		}

		/// <summary>
		/// Discovers the type implementing the given interface type.
		/// </summary>
		/// <param name="type">The interface type.</param>
		/// <param name="includeAbstract">TRUE if the abstract types should be included in the resulting list.</param>
		/// <param name="path">The path to scan.</param>
		/// <returns>A list of runtime type implementing the interface.</returns>
		public static List<T> DiscoverInterface(Type type, bool includeAbstract, string path)
		{
			int count = 0;
			List<T> validTypes = new List<T>();

#if SILVERLIGHT
			string[] assemblyFileNames = Directory.EnumerateFiles(path, "*.dll").ToArray();
            string[] executableFileNames = new string[] { };
#else
			// Include all assemblies and executables.
			string[] assemblyFileNames = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories);
			string[] executableFileNames = Directory.GetFiles(path, "*.exe", SearchOption.AllDirectories);
#endif

            List<string> fileNames = new List<string>();

			// Add assemblies and executables to the list for inspection.
			count = assemblyFileNames.Length;
			for (int i = 0; i < count; i++)
			{
				fileNames.Add(assemblyFileNames[i]);
			}

			count = executableFileNames.Length;
			for (int i = 0; i < count; i++)
			{
				fileNames.Add(executableFileNames[i]);
			}

			// Scan each assembly.
			count = fileNames.Count;
			for (int i = 0; i < count; i++)
			{
                lock (Discovery<T>.LOCK_ASSEMBLY_BLACK_LIST)
                {
					// ... but only if it is not on the black list.
                    if (!Discovery<T>.assembyBlackList.Contains(fileNames[i]))
                    {
                        try
                        {
							// Try to load the assembly ...
                            Assembly assembly = Assembly.LoadFrom(fileNames[i]);
                            validTypes.AddRange(DiscoverInterface(type, assembly, includeAbstract));
                        }
                        catch
                        {
							// ... if something goes wrong add it to the black list.
                            Discovery<T>.assembyBlackList.Add(fileNames[i]);
                        }
                    }
                }
			}

			return validTypes;
		}

		/// <summary>
		/// Discovers the type implementing the given interface type.
		/// </summary>
		/// <param name="type">The interface type.</param>
		/// <param name="assembly">The assembly to scan.</param>
		/// <param name="includeAbstract">TRUE if the abstract types should be included in the resulting list.</param>
		/// <returns>A list of runtime type implementing the interface.</returns>
		public static List<T> DiscoverInterface(Type type, Assembly assembly, bool includeAbstract)
		{
			try
			{
				List<T> validTypes = new List<T>();

				if (assembly != null)
				{
                    List<Type> types;

                    lock (Discovery<T>.LOCK_ASSEMBLY_KNOWN_TYPES)
                    {
						// Has the assembly been inspected before ?
						// If so retrieve it from the cache.
                        if (!Discovery<T>.assembyKnownTypes.TryGetValue(assembly.FullName, out types))
                        {
                            Discovery<T>.assembyKnownTypes[assembly.FullName] = types = assembly.GetTypes().ToList<Type>();
                        }
                    }

					// Match the found types against the interface type we search for.
                    for (int i = 0; i < types.Count; i++)
					{
						if (types[i].GetInterface(type.ToString(), true) != null)
						{
							if ((types[i].IsAbstract && includeAbstract) || !types[i].IsAbstract)
							{
								if (typeof(T) == typeof(Type))
								{
									validTypes.Add(types[i] as T);
								}
								else if (typeof(T) == typeof(string))
								{
									validTypes.Add(types[i].AssemblyQualifiedName as T);
								}
								else
								{
									throw new Exception("Unsupported target list item target type.");
								}
							}
						}
					}
				}

				return validTypes;
			}
			catch
			{
				throw;
			}
		}

		#endregion
	}

	#endregion
}
